Data visualisation
library(tidyverse)
Loading tidyverse: ggplot2
Loading tidyverse: tibble
Loading tidyverse: tidyr
Loading tidyverse: readr
Loading tidyverse: purrr
Loading tidyverse: dplyr
Conflicts with tidy packages -------------------------------------------
filter(): dplyr, stats
lag(): dplyr, stats
Where can I find useful packages?
Where can I find how to use packages
- Reference manual on CRAN
- Vignettes
- ?
- Demos
# List vignettes from all *attached* packages
vignette(all = FALSE)
# List vignettes from all *installed* packages (can take a long time!):
vignette(all = TRUE)
# find vignettes of "ggplot2"
vignette(package = "ggplot2")
# view vignette "ggplot2-specs"
vignette("ggplot2-specs")
now look for more information on ggplot
?ggplot2
demo() # find demos for attached packages
demo(graphics) # A show of some of R's graphics capabilities, run in console
lets look at the some data
note that the pipe can be run in parts (short cut Ctrl+Shift+M, CMD+SHIFT+M )
mpg %>% select(displ, cty, hwy, year) %>% plot()
plot(select(mpg,displ,cty,hwy,year))
Creating a ggplot
ggplot is part of the tidyverse and a widely used package to work with graphics note for ggplot there is “+” to combine commands, in contrast to “% > %” which is the pipe operator for commands outside ggplot
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy))
Create a ggplot with color = class
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy, color = class))
Create a ggplot with size = class
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy, size = class))
Create a ggplot with alpha = class
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy, alpha = class))
Create a ggplot with shape = class
note there are only 6 different shapes, therefore “suv” has no shape and is not displayed
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy, shape = class))
Create plot where property of geom is set manually
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy), color = "blue")
Recap
- Where would you check for packages?
- Where would you look on how to use packages?
- When would you use size as function of a value in a plot?
Geometic objects
different ways to present the same data
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy))
ggplot(data = mpg) +
geom_smooth(mapping = aes(x = displ, y = hwy))
ggplot(data = mpg) +
geom_smooth(mapping = aes(x = displ, y = hwy, linetype = drv, color = drv))
avoid the legend
ggplot(data = mpg) +
geom_smooth(mapping = aes(x = displ, y = hwy, group = drv))
display several geoms in same plot
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy)) +
geom_smooth(mapping = aes(x = displ, y = hwy))
don’t repeat code
ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) +
geom_point(mapping = aes(color = class)) +
geom_smooth()
use only subset of data for geom
ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) +
geom_point(mapping = aes(color = class)) +
geom_smooth(data = filter(mpg, class == "subcompact"), se = FALSE)
Data wrangling
filter rows
filter all rows where month == 1 and day ==1, multiple filter conditions are separated by “,”
filter(flights, month == 1, day == 1)
store all x-mas flights
note, if you wrap the expression in () then the result will be displayed even when the result is assigned to a variable
(xmas_flights <- filter(flights, month == 12, day == 24))
boolean operators work as well
filter(flights, month == 11 | month == 12)
the following expressions give the same result
filter(flights, !(arr_delay > 120 | dep_delay > 120))
filter(flights, arr_delay <= 120, dep_delay <= 120)
Arrange rows with arrange()
arrange(flights, year, month, day)
select columns with select()
also an easy way to bring columns in a specific order
select(flights, year, month, day)
select all but a range of columns
select(flights, -(year:day))
more can be found in the cheatsheet
Add new variables with mutate()
note the %>% operator
select(flights,
year:day,
ends_with("delay"),
distance,
air_time) %>%
mutate(
gain = arr_delay - dep_delay,
speed = distance / air_time * 60,
hours = air_time / 60,
gain_per_hour = gain / hours) %>%
select(-c(month, day, speed))
if you only want to keep the new columns use “transmute()”
select(flights,
year:day,
ends_with("delay"),
distance,
air_time) %>%
transmute(
gain = arr_delay - dep_delay,
speed = distance / air_time * 60,
hours = air_time / 60,
gain_per_hour = gain / hours)
Grouped summaries with summarise()
the mean of all depature delays
summarise(flights, delay = mean(dep_delay, na.rm = TRUE))
# na.rm a logical value indicating whether NA values should be stripped before the computation proceeds.
find pattern of delays during the year

Find planes with high delays
not_cancelled <- flights %>%
filter(!is.na(arr_delay))
not_cancelled %>%
group_by(tailnum) %>%
summarise(
delay = mean(arr_delay)
) %>%
ggplot( mapping = aes(x = delay)) +
geom_freqpoly(binwidth = 10)

there seems a few planes with very high mean delay. Lets look closer into the issue
delays <- not_cancelled %>%
group_by(tailnum) %>%
summarise(
delay = mean(arr_delay, na.rm = TRUE),
n = n()
)
ggplot(data = delays, mapping = aes(x = n, y = delay)) +
geom_point(alpha = 1/10)

the high delays are for tailnum wiht limited number of flight. Lets choose only tailnums where at least 25 flights are recorded
delays %>%
filter(n > 25) %>%
ggplot(mapping = aes(x = n, y = delay)) +
geom_point(alpha = 1/10)

what if we want to select the points under consideration not via a limit but from a plot? Use Shiny Gadgets
library(shiny)
library(miniUI)
ggbrush <- function(data, xvar, yvar) {
ui <- miniPage(
gadgetTitleBar("Drag to select points"),
miniContentPanel(
# The brush="brush" argument means we can listen for
# brush events on the plot using input$brush.
plotOutput("plot", height = "100%", brush = "brush")
)
)
server <- function(input, output, session) {
# Render the plot
output$plot <- renderPlot({
# Plot the data with x/y vars indicated by the caller.
ggplot(data, aes_string(xvar, yvar)) + geom_point()
})
# Handle the Done button being pressed.
observeEvent(input$done, {
# Return the brushed points. See ?shiny::brushedPoints.
stopApp(brushedPoints(data, input$brush, allRows = TRUE))
})
}
runGadget(ui, server)
}
# pick_points(mtcars, ~wt, ~mpg)
brushed_points <- ggbrush(delays, "n", "delay")
Listening on http://127.0.0.1:4198
brushed_points %>% ggplot(mapping = aes(x = n, y = delay, color = selected_)) +
geom_point(alpha = 1/10)

brushed_points %>% filter(selected_ ==TRUE) %>% ggplot(mapping = aes(x = n, y = delay, color = selected_)) +
geom_point(alpha = 1/10)

now a few more things we need for the EuropeLeagueTransfers.Rmd
left_join
the data set nycflights13 has four tibbles (dataframes)
- airlines
- airports
- planes
- weather
airlines
airports
planes
weather
find the links between the data.frames
# this function creates a data.frame with the name of the data.frame and the names of the columns of that data.frame
create_df_of_names = function(df, name){
data.frame(from = name, to = names(df))
}
# create a names list of the data.frames
a <- list(flights = flights,airlines = airlines, airports = airports, weather = weather,
planes = planes)
# and map them to build one data.frame with two columns
# - from contains all data.frame names
# - to contains all column names
edge <- map2_df(a,names(a), create_df_of_names)
Unequal factor levels: coercing to characterUnequal factor levels: coercing to character
# create a visNetwork
nodesFrom <- edge %>% cbind(unlist(.$from),"Table") %>% select(3,4) %>% data.frame
nodesTo <- edge %>% cbind(unlist(.$to),"Attribute") %>% select(3,4) %>% data.frame
names(nodesFrom) <- c("id", "group")
names(nodesTo) <- c("id", "group")
nodes <- rbind(nodesFrom,nodesTo) %>% unique()
nodes$id <- as.character((nodes$id))
nodes <- nodes %>% unique() %>% arrange(id)
visNetwork(nodes, edge)%>%
visOptions(highlightNearest = list(enabled = TRUE, degree = 2), nodesIdSelection = TRUE) %>%
visEdges(arrows = "to") %>%
visGroups(groupname = "Table", shape = "icon", icon = list(code = "f114", color = "green",size = 75)) %>%
visGroups(groupname = "Attribute", shape = "icon", icon = list(code = "f115", color = "lightgreen", size = 45)) %>%
addFontAwesome()
# list of icons http://astronautweb.co/snippet/font-awesome/
lets find out which manufacturer has the highest delays
first we need to join flights with planes
lets find out which airline has the highest delays
first we need to join flights with planes
flight_airlines <- left_join(flights, airlines)
Joining, by = "carrier"
flight_airlines %>% group_by(name) %>% summarise(delay_per_flight = sum(arr_delay, na.rm = TRUE)/ n(),number_of_flights = n()) %>% arrange(desc(delay_per_flight))
long and wide data.frames
for some operations the tidy wide format is not suitable as input to an operation, then a “long” version of the data.frame can be generated using the “melt” command. A further example will be shown in EuropeLeagueTransfers.Rmd and further information on the topic can be found at http://seananderson.ca/2013/10/19/reshape.html
library(reshape2)
names(airquality) <- tolower(names(airquality))
aqm <- melt(airquality, id=c("month", "day"),
variable.name = "climate_variable",
value.name = "climate_value")
airquality
aqm
acast_result[22,5,] # arrays are accessed
ozone solar.r wind temp
23.0 14.0 9.2 71.0
last thing we need for EuropeLeagueTransfers.Rmd
grepl returns a logic vector given an expression
letters
[1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v"
[23] "w" "x" "y" "z"
LS0tCnRpdGxlOiAiUiBLZW5udG5pc3NlIFZIUyAyMDE3LzEiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogNAogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKLS0tCgpUaGlzIGlzIGFuIFtSIE1hcmtkb3duXShodHRwOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tKSBOb3RlYm9vay4gV2hlbiB5b3UgZXhlY3V0ZSBjb2RlIHdpdGhpbiB0aGUgbm90ZWJvb2ssIHRoZSByZXN1bHRzIGFwcGVhciBiZW5lYXRoIHRoZSBjb2RlLiAKClRyeSBleGVjdXRpbmcgdGhpcyBjaHVuayBieSBjbGlja2luZyB0aGUgKlJ1biogYnV0dG9uIHdpdGhpbiB0aGUgY2h1bmsgb3IgYnkgcGxhY2luZyB5b3VyIGN1cnNvciBpbnNpZGUgaXQgYW5kIHByZXNzaW5nICpDbWQrU2hpZnQrRW50ZXIqLiAKCgojIERhdGEgdmlzdWFsaXNhdGlvbgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCnRpZHl2ZXJzZV9wYWNrYWdlcygpICAjIHdoaWNoIHBhY2thZ2VzIGFyZSBpbiB0aWR5dmVyc2UKYGBgCgojIyBXaGVyZSBjYW4gSSBmaW5kIHVzZWZ1bCBwYWNrYWdlcz8KCi0gQ1JBTiB0YXNrIGxpc3QgIGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnCi0gci1ibG9nZ2VycyBzZWFyY2ggaHR0cDovL3d3dy5yLWJsb2dnZXJzLmNvbSAKCiMjIFdoZXJlIGNhbiBJIGZpbmQgaG93IHRvIHVzZSBwYWNrYWdlcwoKLSBSZWZlcmVuY2UgbWFudWFsIG9uIENSQU4KLSBWaWduZXR0ZXMKLSA/Ci0gRGVtb3MKCgoKCmBgYHtyfQojIExpc3QgdmlnbmV0dGVzIGZyb20gYWxsICphdHRhY2hlZCogcGFja2FnZXMKdmlnbmV0dGUoYWxsID0gRkFMU0UpCiMgTGlzdCB2aWduZXR0ZXMgZnJvbSBhbGwgKmluc3RhbGxlZCogcGFja2FnZXMgKGNhbiB0YWtlIGEgbG9uZyB0aW1lISk6CnZpZ25ldHRlKGFsbCA9IFRSVUUpCiMgZmluZCB2aWduZXR0ZXMgb2YgImdncGxvdDIiCnZpZ25ldHRlKHBhY2thZ2UgPSAiZ2dwbG90MiIpCiMgdmlldyB2aWduZXR0ZSAiZ2dwbG90Mi1zcGVjcyIgIAp2aWduZXR0ZSgiZ2dwbG90Mi1zcGVjcyIpCmBgYAoKCm5vdyBsb29rIGZvciBtb3JlIGluZm9ybWF0aW9uIG9uIGdncGxvdAoKYGBge3J9Cj9nZ3Bsb3QyCmRlbW8oKSAgICAgICAgICAjIGZpbmQgZGVtb3MgZm9yIGF0dGFjaGVkIHBhY2thZ2VzCmRlbW8oZ3JhcGhpY3MpICAjIEEgc2hvdyBvZiBzb21lIG9mIFIncyBncmFwaGljcyBjYXBhYmlsaXRpZXMsIHJ1biBpbiBjb25zb2xlCgpgYGAKCgojIyBsZXRzIGxvb2sgYXQgdGhlIHNvbWUgZGF0YQoKbm90ZSB0aGF0IHRoZSBwaXBlIGNhbiBiZSBydW4gaW4gcGFydHMgKHNob3J0IGN1dCBDdHJsK1NoaWZ0K00sIENNRCtTSElGVCtNICkKCmBgYHtyfQptcGcgICU+JSBzZWxlY3QoZGlzcGwsIGN0eSwgaHd5LCB5ZWFyKSAgJT4lIHBsb3QoKQoKcGxvdChzZWxlY3QobXBnLGRpc3BsLGN0eSxod3kseWVhcikpCmBgYAoKCgojIyBDcmVhdGluZyBhIGdncGxvdAoKZ2dwbG90IGlzIHBhcnQgb2YgdGhlIHRpZHl2ZXJzZSBhbmQgYSB3aWRlbHkgdXNlZCBwYWNrYWdlIHRvIHdvcmsgd2l0aCBncmFwaGljcyAKKipub3RlKiogZm9yIGdncGxvdCB0aGVyZSBpcyAiKyIgdG8gY29tYmluZSBjb21tYW5kcywgaW4gY29udHJhc3QgdG8gIiUgPiAlIiB3aGljaCBpcyB0aGUgcGlwZSBvcGVyYXRvciBmb3IgY29tbWFuZHMgb3V0c2lkZSBnZ3Bsb3QKCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBtcGcpICsgCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBkaXNwbCwgeSA9IGh3eSkpCmBgYAoKCiMjIENyZWF0ZSBhIGdncGxvdCB3aXRoIGNvbG9yID0gY2xhc3MKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IG1wZykgKyAKICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IGRpc3BsLCB5ID0gaHd5LCBjb2xvciA9IGNsYXNzKSkKYGBgCgojIyBDcmVhdGUgYSBnZ3Bsb3Qgd2l0aCBzaXplID0gY2xhc3MKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IG1wZykgKyAKICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IGRpc3BsLCB5ID0gaHd5LCBzaXplID0gY2xhc3MpKQpgYGAKCiMjIENyZWF0ZSBhIGdncGxvdCB3aXRoIGFscGhhID0gY2xhc3MKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IG1wZykgKyAKICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IGRpc3BsLCB5ID0gaHd5LCBhbHBoYSA9IGNsYXNzKSkKYGBgCgojIyBDcmVhdGUgYSBnZ3Bsb3Qgd2l0aCBzaGFwZSA9IGNsYXNzCgoqKm5vdGUqKiB0aGVyZSBhcmUgb25seSA2IGRpZmZlcmVudCBzaGFwZXMsIHRoZXJlZm9yZSAic3V2IiBoYXMgbm8gc2hhcGUgYW5kIGlzIG5vdCBkaXNwbGF5ZWQKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IG1wZykgKyAKICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IGRpc3BsLCB5ID0gaHd5LCBzaGFwZSA9IGNsYXNzKSkKYGBgCgoKCiMjIENyZWF0ZSBwbG90IHdoZXJlIHByb3BlcnR5IG9mIGdlb20gaXMgc2V0IG1hbnVhbGx5CgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBtcGcpICsgCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBkaXNwbCwgeSA9IGh3eSksIGNvbG9yID0gImJsdWUiKQpgYGAKCiMjIFJlY2FwCi0gV2hlcmUgd291bGQgeW91IGNoZWNrIGZvciBwYWNrYWdlcz8KLSBXaGVyZSB3b3VsZCB5b3UgbG9vayBvbiBob3cgdG8gdXNlIHBhY2thZ2VzPwotIFdoZW4gd291bGQgeW91IHVzZSBzaXplIGFzIGZ1bmN0aW9uIG9mIGEgdmFsdWUgaW4gYSBwbG90PwoKCiMgRmFjZXRzCklmIHRoZXJlIGlzIGEgdmFyaWFibGUgdmFsdWUgd2hpY2ggc2VwYXJhdGVzIGRhdGEgaXQgY2FuIGJlIHVzZWQgdG8gY3JlYXRlIG11bHRpcGxlIHBsb3RzIHJhdGhlciB0aGFuIG11bHRpcGxlIGxpbmVzIGluIG9uZSBwbG90LgoKIyMgZmFjZXRfd3JhcApmYWNldF93cmFwIHdyYXBzIGEgMWQgc2VxdWVuY2Ugb2YgcGFuZWxzIGludG8gMmQKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IG1wZykgKyAKICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IGRpc3BsLCB5ID0gaHd5KSkgKyAKICBmYWNldF93cmFwKH4gY2xhc3MsIG5yb3cgPSAyKQpgYGAKCgojIyBmYWNldF9ncmlkCmZhY2V0X2dyaWQgZm9ybXMgYSBtYXRyaXggb2YgcGFuZWxzIGRlZmluZWQgYnkgcm93IGFuZCBjb2x1bW4gZmFjZXR0aW5nIHZhcmlhYmxlcy4KCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IG1wZykgKyAKICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IGRpc3BsLCB5ID0gaHd5KSkgKyAKICBmYWNldF9ncmlkKGRydiB+IGN5bCkKYGBgCgoKCiMgR2VvbWV0aWMgb2JqZWN0cwpkaWZmZXJlbnQgd2F5cyB0byBwcmVzZW50IHRoZSBzYW1lIGRhdGEKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IG1wZykgKyAKICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IGRpc3BsLCB5ID0gaHd5KSkgCmBgYAoKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IG1wZykgKyAKICBnZW9tX3Ntb290aChtYXBwaW5nID0gYWVzKHggPSBkaXNwbCwgeSA9IGh3eSkpCmBgYAoKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IG1wZykgKyAKICBnZW9tX3Ntb290aChtYXBwaW5nID0gYWVzKHggPSBkaXNwbCwgeSA9IGh3eSwgbGluZXR5cGUgPSBkcnYsIGNvbG9yID0gZHJ2KSkKYGBgCgojIyMgYXZvaWQgdGhlIGxlZ2VuZAoKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IG1wZykgKwogIGdlb21fc21vb3RoKG1hcHBpbmcgPSBhZXMoeCA9IGRpc3BsLCB5ID0gaHd5LCBncm91cCA9IGRydikpCmBgYAoKCiMjIGRpc3BsYXkgc2V2ZXJhbCBnZW9tcyBpbiBzYW1lIHBsb3QKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IG1wZykgKyAKICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IGRpc3BsLCB5ID0gaHd5KSkgKwogIGdlb21fc21vb3RoKG1hcHBpbmcgPSBhZXMoeCA9IGRpc3BsLCB5ID0gaHd5KSkKYGBgCgoKIyMgZG9uJ3QgcmVwZWF0IGNvZGUgCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBtcGcsIG1hcHBpbmcgPSBhZXMoeCA9IGRpc3BsLCB5ID0gaHd5KSkgKyAKICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoY29sb3IgPSBjbGFzcykpICsgCiAgZ2VvbV9zbW9vdGgoKQpgYGAKCgojIyB1c2Ugb25seSBzdWJzZXQgb2YgZGF0YSBmb3IgZ2VvbQoKYGBge3J9CmdncGxvdChkYXRhID0gbXBnLCBtYXBwaW5nID0gYWVzKHggPSBkaXNwbCwgeSA9IGh3eSkpICsgCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKGNvbG9yID0gY2xhc3MpKSArIAogIGdlb21fc21vb3RoKGRhdGEgPSBmaWx0ZXIobXBnLCBjbGFzcyA9PSAic3ViY29tcGFjdCIpLCBzZSA9IEZBTFNFKQpgYGAKCgojIyBsb3N0IGluIGFsbCB0aGUgb3B0aW9ucz8KQ0hFQVRTSEVFVFMgYXJlIGF0IHlvdXIgZmluZ2VydGlwcyB1bmRlciBIRUxQIG1lbnUgb2YgUlN0dWRpbyBJREUgb3IKaHR0cHM6Ly93d3cucnN0dWRpby5jb20vcmVzb3VyY2VzL2NoZWF0c2hlZXRzLyAKCgojIFN0YXRpc3RpY2FsIHRyYW5zZm9ybWF0aW9ucwoKIyMgYmFyIHBsb3QgZm9yIGRpc2NyZXRlIHgtZGF0YQpgYGB7cn0KZ2dwbG90KGRhdGEgPSBkaWFtb25kcykgKyAKICBnZW9tX2JhcihtYXBwaW5nID0gYWVzKHggPSBjdXQpKQpgYGAKCiMjIGJveCBwbG90IGZvciBkaXNjcmV0ZSB4LSBhbmQgY29udGludW91cyB5LWRhdGEKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGRpYW1vbmRzKSArIAogIGdlb21fYm94cGxvdChtYXBwaW5nID0gYWVzKHggPSBjdXQsIHkgPSBwcmljZSkpCmBgYAoKCiMjIFZpb2xpbiBwbG90IGZvciBkaXNjcmV0ZSB4LSBhbmQgY29udGludW91cyB5LWRhdGEKZ2l2ZXMgZ29vZCBpbXByZXNzaW9uIG9mIGRpc3RyaWJ1dGlvbgoKYGBge3J9CmdncGxvdChkYXRhID0gZGlhbW9uZHMpICsgCiAgZ2VvbV92aW9saW4obWFwcGluZyA9IGFlcyh4ID0gY3V0LCB5ID0gcHJpY2UsIGNvbG9yID0gY3V0KSkKYGBgCgojIyBIaXN0b2dyYW0KQSBoaXN0b2dyYW0gaXMgYSBncmFwaGljYWwgcmVwcmVzZW50YXRpb24gb2YgdGhlIGRpc3RyaWJ1dGlvbiBvZiBudW1lcmljYWwgZGF0YS4KCmh0dHBzOi8vZGUud2lraXBlZGlhLm9yZy93aWtpL0hpc3RvZ3JhbW0KCmBgYHtyfQpnZ3Bsb3QoZGlhbW9uZHMsIGFlcyhjYXJhdCkpICsKICBnZW9tX2hpc3RvZ3JhbSgpCiMgc2V0IGJpbndpZHRoCmdncGxvdChkaWFtb25kcywgYWVzKGNhcmF0KSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC4wMSkKIyBzZXQgbnVtYmVyIG9mIGJpbnMKZ2dwbG90KGRpYW1vbmRzLCBhZXMoY2FyYXQpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDIwMCkKYGBgCgojIyB1c2UgZ2VvbV9mcmVxcG9seSBmb3IgZWFzaWVyIGNvbXBhcmlzb24KCmBgYHtyfQojIFJhdGhlciB0aGFuIHN0YWNraW5nIGhpc3RvZ3JhbXMsIGl0J3MgZWFzaWVyIHRvIGNvbXBhcmUgZnJlcXVlbmN5CiMgcG9seWdvbnMKZ2dwbG90KGRpYW1vbmRzLCBhZXMocHJpY2UsIGZpbGwgPSBjdXQpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSA1MDApCmdncGxvdChkaWFtb25kcywgYWVzKHByaWNlLCBjb2xvdXIgPSBjdXQpKSArCiAgZ2VvbV9mcmVxcG9seShiaW53aWR0aCA9IDUwMCkKYGBgCgoKd29yayB3aXRoIGRlbnNpdGllcywgbWVhbnMgZWFjaCBjdXJ2ZSBoYXMgYXJlYSBvZiBvbmUKCmBgYHtyfQojIFRvIG1ha2UgaXQgZWFzaWVyIHRvIGNvbXBhcmUgZGlzdHJpYnV0aW9ucyB3aXRoIHZlcnkgZGlmZmVyZW50IGNvdW50cywKIyBwdXQgZGVuc2l0eSBvbiB0aGUgeSBheGlzIGluc3RlYWQgb2YgdGhlIGRlZmF1bHQgY291bnQKZ2dwbG90KGRpYW1vbmRzLCBhZXMocHJpY2UsIC4uZGVuc2l0eS4uLCBjb2xvdXIgPSBjdXQpKSArCiAgZ2VvbV9mcmVxcG9seShiaW53aWR0aCA9IDUwMCkKYGBgCgojIyBFbXBpcmljYWwgQ3VtdWxhdGl2ZSBEaXN0cmlidXRpb24gRnVuY3Rpb24gKEVDREYpCgpUaGUgZW1waXJpY2FsIGRpc3RyaWJ1dGlvbiBmdW5jdGlvbiBlc3RpbWF0ZXMgdGhlIGN1bXVsYXRpdmUgZGlzdHJpYnV0aW9uIGZ1bmN0aW9uIHVuZGVybHlpbmcgb2YgdGhlIHBvaW50cyBpbiB0aGUgc2FtcGxlIGFuZCBjb252ZXJnZXMgd2l0aCBwcm9iYWJpbGl0eSAxCgpodHRwczovL2RlLndpa2lwZWRpYS5vcmcvd2lraS9FbXBpcmlzY2hlX1ZlcnRlaWx1bmdzZnVua3Rpb24KCgpgYGB7cn0KZGYgPC0gZGF0YS5mcmFtZSh4ID0gcm5vcm0oMTAwMDApKQpnZ3Bsb3QoZGYsIGFlcyh4KSkgKwogIGdlb21faGlzdG9ncmFtKCkKZ2dwbG90KGRmLCBhZXMoeCkpICsgc3RhdF9lY2RmKGdlb20gPSAic3RlcCIpCgpwICA8LSBnZ3Bsb3QoZGYsIGFlcyh4KSkgKyBzdGF0X2VjZGYoKQpwZyA8LSBnZ3Bsb3RfYnVpbGQocCkkZGF0YVtbMV1dCmdncGxvdChwZywgYWVzKHggPSB4LCB5ID0gMS15ICkpICsgZ2VvbV9zdGVwKCkgKyBzY2FsZV95X2xvZzEwKCkgCgoKCmBgYAoKCiMjIFJlY2FwCgotIFdoaWNoIGdlb20gc2VlbXMgdXNlZnVsIGZvciB5b3U/Ci0gQ2FuIHlvdSB0aGluayBvZiBhIHVzZSBjYXNlIGZvciBhIGZhY2V0IHBsb3Q/CgpvbmUgbW9yZSBzb3VyY2UgZm9yIGluZm9ybWF0aW9uIGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZwoKIyAgRGF0YSB3cmFuZ2xpbmcKCmBgYHtyfQpsaWJyYXJ5KG55Y2ZsaWdodHMxMykKZmxpZ2h0cwpgYGAKCgojIyBmaWx0ZXIgcm93cwoKZmlsdGVyIGFsbCByb3dzIHdoZXJlIG1vbnRoID09IDEgYW5kIGRheSA9PTEsIG11bHRpcGxlIGZpbHRlciBjb25kaXRpb25zIGFyZSBzZXBhcmF0ZWQgYnkgIiwiCgpgYGB7cn0KZmlsdGVyKGZsaWdodHMsIG1vbnRoID09IDEsIGRheSA9PSAxKQpgYGAKCgojIyBzdG9yZSBhbGwgeC1tYXMgZmxpZ2h0cwoKbm90ZSwgaWYgeW91IHdyYXAgdGhlIGV4cHJlc3Npb24gaW4gKCkgdGhlbiB0aGUgcmVzdWx0IHdpbGwgYmUgZGlzcGxheWVkIGV2ZW4gd2hlbiB0aGUgcmVzdWx0IGlzIGFzc2lnbmVkIHRvIGEgdmFyaWFibGUKCmBgYHtyfQooeG1hc19mbGlnaHRzIDwtIGZpbHRlcihmbGlnaHRzLCBtb250aCA9PSAxMiwgZGF5ID09IDI0KSkKYGBgCgoKIyMgYm9vbGVhbiBvcGVyYXRvcnMgd29yayBhcyB3ZWxsCgpgYGB7cn0KZmlsdGVyKGZsaWdodHMsIG1vbnRoID09IDExIHwgbW9udGggPT0gMTIpCmBgYAoKCnRoZSBmb2xsb3dpbmcgZXhwcmVzc2lvbnMgZ2l2ZSB0aGUgc2FtZSByZXN1bHQKCgpgYGB7cn0KZmlsdGVyKGZsaWdodHMsICEoYXJyX2RlbGF5ID4gMTIwIHwgZGVwX2RlbGF5ID4gMTIwKSkKZmlsdGVyKGZsaWdodHMsIGFycl9kZWxheSA8PSAxMjAsIGRlcF9kZWxheSA8PSAxMjApCmBgYAoKCiMjIEFycmFuZ2Ugcm93cyB3aXRoIGFycmFuZ2UoKQoKCmBgYHtyfQphcnJhbmdlKGZsaWdodHMsIHllYXIsIG1vbnRoLCBkYXkpCmBgYAoKIyMgc2VsZWN0IGNvbHVtbnMgd2l0aCBzZWxlY3QoKQphbHNvIGFuIGVhc3kgd2F5IHRvIGJyaW5nIGNvbHVtbnMgaW4gYSBzcGVjaWZpYyBvcmRlcgoKYGBge3J9CnNlbGVjdChmbGlnaHRzLCB5ZWFyLCBtb250aCwgZGF5KQpgYGAKc2VsZWN0IGFsbCBidXQgYSByYW5nZSBvZiBjb2x1bW5zCgpgYGB7cn0Kc2VsZWN0KGZsaWdodHMsIC0oeWVhcjpkYXkpKQpgYGAKCm1vcmUgY2FuIGJlIGZvdW5kIGluIHRoZSBjaGVhdHNoZWV0IAoKIyMgQWRkIG5ldyB2YXJpYWJsZXMgd2l0aCBtdXRhdGUoKQoKbm90ZSB0aGUgJT4lIG9wZXJhdG9yCgpgYGB7cn0Kc2VsZWN0KGZsaWdodHMsIAogIHllYXI6ZGF5LCAKICBlbmRzX3dpdGgoImRlbGF5IiksIAogIGRpc3RhbmNlLCAKICBhaXJfdGltZSkgJT4lIAptdXRhdGUoCiAgZ2FpbiA9IGFycl9kZWxheSAtIGRlcF9kZWxheSwKICBzcGVlZCA9IGRpc3RhbmNlIC8gYWlyX3RpbWUgKiA2MCwKICBob3VycyA9IGFpcl90aW1lIC8gNjAsCiAgZ2Fpbl9wZXJfaG91ciA9IGdhaW4gLyBob3VycykgJT4lIAogIHNlbGVjdCgtYyhtb250aCwgZGF5LCBzcGVlZCkpCmBgYAoKaWYgeW91IG9ubHkgd2FudCB0byBrZWVwIHRoZSBuZXcgY29sdW1ucyB1c2UgInRyYW5zbXV0ZSgpIgoKYGBge3J9CnNlbGVjdChmbGlnaHRzLCAKICB5ZWFyOmRheSwgCiAgZW5kc193aXRoKCJkZWxheSIpLCAKICBkaXN0YW5jZSwgCiAgYWlyX3RpbWUpICU+JSAKdHJhbnNtdXRlKAogIGdhaW4gPSBhcnJfZGVsYXkgLSBkZXBfZGVsYXksCiAgc3BlZWQgPSBkaXN0YW5jZSAvIGFpcl90aW1lICogNjAsCiAgaG91cnMgPSBhaXJfdGltZSAvIDYwLAogIGdhaW5fcGVyX2hvdXIgPSBnYWluIC8gaG91cnMpIApgYGAKCgojIyBHcm91cGVkIHN1bW1hcmllcyB3aXRoIHN1bW1hcmlzZSgpCgp0aGUgbWVhbiBvZiBhbGwgZGVwYXR1cmUgZGVsYXlzCgpgYGB7cn0Kc3VtbWFyaXNlKGZsaWdodHMsIGRlbGF5ID0gbWVhbihkZXBfZGVsYXksIG5hLnJtID0gVFJVRSkpCgojIG5hLnJtCWEgbG9naWNhbCB2YWx1ZSBpbmRpY2F0aW5nIHdoZXRoZXIgTkEgdmFsdWVzIHNob3VsZCBiZSBzdHJpcHBlZCBiZWZvcmUgdGhlIGNvbXB1dGF0aW9uIHByb2NlZWRzLgoKYGBgCgoKCmBgYHtyfQpieV9kYXkgPC0gZ3JvdXBfYnkoZmxpZ2h0cywgeWVhciwgbW9udGgsIGRheSkKc3VtbWFyaXNlKGJ5X2RheSwgZGVsYXkgPSBtZWFuKGRlcF9kZWxheSwgbmEucm0gPSBUUlVFKSkKYGBgCgpmaW5kIHBhdHRlcm4gb2YgZGVsYXlzIGR1cmluZyB0aGUgeWVhcgoKYGBge3J9CmJ5X2RheSA8LSBmbGlnaHRzICU+JSBncm91cF9ieSh5ZWFyLCBtb250aCkKc3VtbWFyaXNlKGJ5X2RheSwgZGVsYXkgPSBtZWFuKGRlcF9kZWxheSwgbmEucm0gPSBUUlVFKSkgJT4lIGdncGxvdChhZXMoIHggPSBtb250aCwgeSA9IGRlbGF5LCBncm91cCA9IG1vbnRoKSkgKwogIGdlb21fY29sKCkKYGBgCgoKCiMjIEZpbmQgcGxhbmVzIHdpdGggaGlnaCBkZWxheXMKCmBgYHtyfQpub3RfY2FuY2VsbGVkIDwtIGZsaWdodHMgJT4lIAogIGZpbHRlcighaXMubmEoYXJyX2RlbGF5KSkKCm5vdF9jYW5jZWxsZWQgJT4lIAogIGdyb3VwX2J5KHRhaWxudW0pICU+JSAKICBzdW1tYXJpc2UoCiAgICBkZWxheSA9IG1lYW4oYXJyX2RlbGF5KQogICkgJT4lCmdncGxvdCggbWFwcGluZyA9IGFlcyh4ID0gZGVsYXkpKSArIAogIGdlb21fZnJlcXBvbHkoYmlud2lkdGggPSAxMCkKYGBgCgp0aGVyZSBzZWVtcyBhIGZldyBwbGFuZXMgd2l0aCB2ZXJ5IGhpZ2ggbWVhbiBkZWxheS4gTGV0cyBsb29rIGNsb3NlciBpbnRvIHRoZSBpc3N1ZQoKYGBge3J9CmRlbGF5cyA8LSBub3RfY2FuY2VsbGVkICU+JSAKICBncm91cF9ieSh0YWlsbnVtKSAlPiUgCiAgc3VtbWFyaXNlKAogICAgZGVsYXkgPSBtZWFuKGFycl9kZWxheSwgbmEucm0gPSBUUlVFKSwKICAgIG4gPSBuKCkKICApCgpnZ3Bsb3QoZGF0YSA9IGRlbGF5cywgbWFwcGluZyA9IGFlcyh4ID0gbiwgeSA9IGRlbGF5KSkgKyAKICBnZW9tX3BvaW50KGFscGhhID0gMS8xMCkKYGBgCgoKdGhlIGhpZ2ggZGVsYXlzIGFyZSBmb3IgdGFpbG51bSB3aWh0IGxpbWl0ZWQgbnVtYmVyIG9mIGZsaWdodC4KTGV0cyBjaG9vc2Ugb25seSB0YWlsbnVtcyB3aGVyZSBhdCBsZWFzdCAyNSBmbGlnaHRzIGFyZSByZWNvcmRlZAoKYGBge3J9CmRlbGF5cyAlPiUgCiAgZmlsdGVyKG4gPiAyNSkgJT4lIAogIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSBuLCB5ID0gZGVsYXkpKSArIAogICAgZ2VvbV9wb2ludChhbHBoYSA9IDEvMTApCmBgYAoKd2hhdCBpZiB3ZSB3YW50IHRvIHNlbGVjdCB0aGUgcG9pbnRzIHVuZGVyIGNvbnNpZGVyYXRpb24gbm90IHZpYSBhIGxpbWl0IGJ1dCBmcm9tIGEgcGxvdD8gVXNlICoqU2hpbnkgR2FkZ2V0cyoqCgpgYGB7cn0KbGlicmFyeShzaGlueSkKbGlicmFyeShtaW5pVUkpCgpnZ2JydXNoIDwtIGZ1bmN0aW9uKGRhdGEsIHh2YXIsIHl2YXIpIHsKICAKICB1aSA8LSBtaW5pUGFnZSgKICAgIGdhZGdldFRpdGxlQmFyKCJEcmFnIHRvIHNlbGVjdCBwb2ludHMiKSwKICAgIG1pbmlDb250ZW50UGFuZWwoCiAgICAgICMgVGhlIGJydXNoPSJicnVzaCIgYXJndW1lbnQgbWVhbnMgd2UgY2FuIGxpc3RlbiBmb3IKICAgICAgIyBicnVzaCBldmVudHMgb24gdGhlIHBsb3QgdXNpbmcgaW5wdXQkYnJ1c2guCiAgICAgIHBsb3RPdXRwdXQoInBsb3QiLCBoZWlnaHQgPSAiMTAwJSIsIGJydXNoID0gImJydXNoIikKICAgICkKICApCiAgCiAgc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQsIHNlc3Npb24pIHsKICAgIAogICAgIyBSZW5kZXIgdGhlIHBsb3QKICAgIG91dHB1dCRwbG90IDwtIHJlbmRlclBsb3QoewogICAgICAjIFBsb3QgdGhlIGRhdGEgd2l0aCB4L3kgdmFycyBpbmRpY2F0ZWQgYnkgdGhlIGNhbGxlci4KICAgICAgZ2dwbG90KGRhdGEsIGFlc19zdHJpbmcoeHZhciwgeXZhcikpICsgZ2VvbV9wb2ludCgpCiAgICB9KQogICAgCiAgICAjIEhhbmRsZSB0aGUgRG9uZSBidXR0b24gYmVpbmcgcHJlc3NlZC4KICAgIG9ic2VydmVFdmVudChpbnB1dCRkb25lLCB7CiAgICAgICMgUmV0dXJuIHRoZSBicnVzaGVkIHBvaW50cy4gU2VlID9zaGlueTo6YnJ1c2hlZFBvaW50cy4KICAgICAgc3RvcEFwcChicnVzaGVkUG9pbnRzKGRhdGEsIGlucHV0JGJydXNoLCBhbGxSb3dzID0gVFJVRSkpCiAgICB9KQogIH0KICAKICBydW5HYWRnZXQodWksIHNlcnZlcikKfQojIHBpY2tfcG9pbnRzKG10Y2Fycywgfnd0LCB+bXBnKQpicnVzaGVkX3BvaW50cyA8LSBnZ2JydXNoKGRlbGF5cywgIm4iLCAiZGVsYXkiKQoKYnJ1c2hlZF9wb2ludHMgICAlPiUgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IG4sIHkgPSBkZWxheSwgY29sb3IgPSBzZWxlY3RlZF8pKSArIAogICAgZ2VvbV9wb2ludChhbHBoYSA9IDEvMTApCgpicnVzaGVkX3BvaW50cyAgICU+JSBmaWx0ZXIoc2VsZWN0ZWRfID09VFJVRSkgICU+JSAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IG4sIHkgPSBkZWxheSwgY29sb3IgPSBzZWxlY3RlZF8pKSArIAogICAgZ2VvbV9wb2ludChhbHBoYSA9IDEvMykKCmBgYAoKCgojIyBub3cgYSBmZXcgbW9yZSB0aGluZ3Mgd2UgbmVlZCBmb3IgdGhlIEV1cm9wZUxlYWd1ZVRyYW5zZmVycy5SbWQKCiMjIyBsZWZ0X2pvaW4KCnRoZSBkYXRhIHNldCBueWNmbGlnaHRzMTMgaGFzIGZvdXIgdGliYmxlcyAoZGF0YWZyYW1lcykKCi0gYWlybGluZXMKLSBhaXJwb3J0cwotIHBsYW5lcwotIHdlYXRoZXIKCgpgYGB7cn0KIGFpcmxpbmVzCiBhaXJwb3J0cwogcGxhbmVzCiB3ZWF0aGVyCmBgYAoKCiMjIGZpbmQgdGhlIGxpbmtzIGJldHdlZW4gdGhlIGRhdGEuZnJhbWVzCgoKYGBge3J9CmxpYnJhcnkodmlzTmV0d29yaykKIyB0aGlzIGZ1bmN0aW9uIGNyZWF0ZXMgYSBkYXRhLmZyYW1lIHdpdGggdGhlIG5hbWUgb2YgdGhlIGRhdGEuZnJhbWUgYW5kIHRoZSBuYW1lcyBvZiB0aGUgY29sdW1ucyBvZiB0aGF0IGRhdGEuZnJhbWUKY3JlYXRlX2RmX29mX25hbWVzID0gZnVuY3Rpb24oZGYsIG5hbWUpewogIGRhdGEuZnJhbWUoZnJvbSA9IG5hbWUsIHRvID0gbmFtZXMoZGYpKQp9CgojIGNyZWF0ZSBhIG5hbWVzIGxpc3Qgb2YgdGhlIGRhdGEuZnJhbWVzCmEgPC0gbGlzdChmbGlnaHRzID0gZmxpZ2h0cyxhaXJsaW5lcyA9IGFpcmxpbmVzLCBhaXJwb3J0cyA9IGFpcnBvcnRzLCB3ZWF0aGVyID0gd2VhdGhlciwKICAgICAgICAgIHBsYW5lcyA9IHBsYW5lcykgCiMgYW5kIG1hcCB0aGVtIHRvIGJ1aWxkIG9uZSBkYXRhLmZyYW1lIHdpdGggdHdvIGNvbHVtbnMKIyAtIGZyb20gY29udGFpbnMgYWxsICBkYXRhLmZyYW1lIG5hbWVzCiMgLSB0byAgY29udGFpbnMgYWxsIGNvbHVtbiBuYW1lcwplZGdlIDwtIG1hcDJfZGYoYSxuYW1lcyhhKSwgY3JlYXRlX2RmX29mX25hbWVzKQoKIyBjcmVhdGUgYSB2aXNOZXR3b3JrCgpub2Rlc0Zyb20gPC0gIGVkZ2UgJT4lIGNiaW5kKHVubGlzdCguJGZyb20pLCJUYWJsZSIpICU+JSBzZWxlY3QoMyw0KSAlPiUgZGF0YS5mcmFtZSAgCm5vZGVzVG8gPC0gIGVkZ2UgJT4lIGNiaW5kKHVubGlzdCguJHRvKSwiQXR0cmlidXRlIikgJT4lIHNlbGVjdCgzLDQpICU+JSBkYXRhLmZyYW1lIAoKbmFtZXMobm9kZXNGcm9tKSA8LSBjKCJpZCIsICJncm91cCIpCm5hbWVzKG5vZGVzVG8pIDwtIGMoImlkIiwgImdyb3VwIikKCm5vZGVzIDwtIHJiaW5kKG5vZGVzRnJvbSxub2Rlc1RvKSAlPiUgdW5pcXVlKCkgCm5vZGVzJGlkIDwtIGFzLmNoYXJhY3Rlcigobm9kZXMkaWQpKSAgCm5vZGVzIDwtIG5vZGVzICU+JSB1bmlxdWUoKSAlPiUgYXJyYW5nZShpZCkKdmlzTmV0d29yayhub2RlcywgZWRnZSklPiUKICB2aXNPcHRpb25zKGhpZ2hsaWdodE5lYXJlc3QgPSBsaXN0KGVuYWJsZWQgPSBUUlVFLCBkZWdyZWUgPSAyKSwgbm9kZXNJZFNlbGVjdGlvbiA9IFRSVUUpICU+JQogIHZpc0VkZ2VzKGFycm93cyA9ICJ0byIpICU+JSAgCiAgdmlzR3JvdXBzKGdyb3VwbmFtZSA9ICJUYWJsZSIsICAgICBzaGFwZSA9ICJpY29uIiwgaWNvbiA9IGxpc3QoY29kZSA9ICJmMTE0IiwgY29sb3IgPSAiZ3JlZW4iLHNpemUgPSA3NSkpICU+JQogIHZpc0dyb3Vwcyhncm91cG5hbWUgPSAiQXR0cmlidXRlIiwgc2hhcGUgPSAiaWNvbiIsIGljb24gPSBsaXN0KGNvZGUgPSAiZjExNSIsIGNvbG9yID0gImxpZ2h0Z3JlZW4iLCBzaXplID0gNDUpKSAlPiUKICBhZGRGb250QXdlc29tZSgpIAojIGxpc3Qgb2YgaWNvbnMgaHR0cDovL2FzdHJvbmF1dHdlYi5jby9zbmlwcGV0L2ZvbnQtYXdlc29tZS8KCmBgYAoKIyMgbGV0cyBmaW5kIG91dCB3aGljaCBtYW51ZmFjdHVyZXIgaGFzIHRoZSBoaWdoZXN0IGRlbGF5cwoKZmlyc3Qgd2UgbmVlZCB0byBqb2luIGZsaWdodHMgd2l0aCBwbGFuZXMKCmBgYHtyfQpmbGlnaHRfcGxhbmVzIDwtIGxlZnRfam9pbihmbGlnaHRzLCBwbGFuZXMsIGJ5ID0gInRhaWxudW0iKQoKZmxpZ2h0X3BsYW5lcyAlPiUgZ3JvdXBfYnkobWFudWZhY3R1cmVyKSAlPiUgc3VtbWFyaXNlKGRlbGF5X3Blcl9mbGlnaHQgPSBzdW0oYXJyX2RlbGF5LCBuYS5ybSA9IFRSVUUpLyBuKCksbnVtYmVyX29mX2ZsaWdodHMgPSBuKCkpICU+JSBhcnJhbmdlKGRlc2MoZGVsYXlfcGVyX2ZsaWdodCkpCgpgYGAKCiMjIGxldHMgZmluZCBvdXQgd2hpY2ggYWlybGluZSBoYXMgdGhlIGhpZ2hlc3QgZGVsYXlzCmZpcnN0IHdlIG5lZWQgdG8gam9pbiBmbGlnaHRzIHdpdGggcGxhbmVzCgpgYGB7cn0KZmxpZ2h0X2FpcmxpbmVzIDwtIGxlZnRfam9pbihmbGlnaHRzLCBhaXJsaW5lcykKCmZsaWdodF9haXJsaW5lcyAlPiUgZ3JvdXBfYnkobmFtZSkgJT4lIHN1bW1hcmlzZShkZWxheV9wZXJfZmxpZ2h0ID0gc3VtKGFycl9kZWxheSwgbmEucm0gPSBUUlVFKS8gbigpLG51bWJlcl9vZl9mbGlnaHRzID0gbigpKSAlPiUgYXJyYW5nZShkZXNjKGRlbGF5X3Blcl9mbGlnaHQpKQoKYGBgCgoKCiMjIGxvbmcgYW5kIHdpZGUgZGF0YS5mcmFtZXMKCmZvciBzb21lIG9wZXJhdGlvbnMgdGhlIHRpZHkgd2lkZSBmb3JtYXQgaXMgbm90IHN1aXRhYmxlIGFzIGlucHV0IHRvIGFuIG9wZXJhdGlvbiwgdGhlbiBhICJsb25nIiB2ZXJzaW9uIG9mIHRoZSBkYXRhLmZyYW1lIGNhbiBiZSBnZW5lcmF0ZWQgdXNpbmcgdGhlICJtZWx0IiBjb21tYW5kLgpBIGZ1cnRoZXIgZXhhbXBsZSB3aWxsIGJlIHNob3duIGluICoqRXVyb3BlTGVhZ3VlVHJhbnNmZXJzLlJtZCoqIGFuZCBmdXJ0aGVyIGluZm9ybWF0aW9uIG9uIHRoZSB0b3BpYyBjYW4gYmUgZm91bmQgYXQgaHR0cDovL3NlYW5hbmRlcnNvbi5jYS8yMDEzLzEwLzE5L3Jlc2hhcGUuaHRtbCAKCmBgYHtyfQpsaWJyYXJ5KHJlc2hhcGUyKQpuYW1lcyhhaXJxdWFsaXR5KSA8LSB0b2xvd2VyKG5hbWVzKGFpcnF1YWxpdHkpKQphcW0gPC0gbWVsdChhaXJxdWFsaXR5LCBpZD1jKCJtb250aCIsICJkYXkiKSwKICB2YXJpYWJsZS5uYW1lID0gImNsaW1hdGVfdmFyaWFibGUiLCAKICB2YWx1ZS5uYW1lID0gImNsaW1hdGVfdmFsdWUiKQphaXJxdWFsaXR5CmFxbQpgYGAKCgpgYGB7cn0KKGFjYXN0X3Jlc3VsdCA8LSBhY2FzdChhcW0sIGRheSB+IG1vbnRoIH4gY2xpbWF0ZV92YXJpYWJsZSwgbmEucm0gPSBUUlVFKSkKYWNhc3QoYXFtLCBtb250aCB+IGNsaW1hdGVfdmFyaWFibGUsIG1lYW4sIG5hLnJtID0gVFJVRSkKYWNhc3RfcmVzdWx0CmFjYXN0X3Jlc3VsdFsyMiw1LF0gICMgYXJyYXlzIGFyZSBhY2Nlc3NlZCAKYGBgCgoKIyMgbGFzdCB0aGluZyB3ZSBuZWVkIGZvciBFdXJvcGVMZWFndWVUcmFuc2ZlcnMuUm1kCgpncmVwbCByZXR1cm5zIGEgbG9naWMgdmVjdG9yIGdpdmVuIGFuIGV4cHJlc3Npb24KCmBgYHtyfQpsZXR0ZXJzCmdyZXAoIlthLWNdIiwgbGV0dGVycykKZ3JlcCgiW2Etel0iLCBsZXR0ZXJzKQpncmVwbCgiW2EtY10iLCBsZXR0ZXJzKQpncmVwbCgiW2Etel0iLCBsZXR0ZXJzKQoKYGBgCgoKIyBMZXRzIGRpdmUgaW50byBzb21lIGNvZGUKCioqRXVyb3BlTGVhZ3VlVHJhbnNmZXJzLlJtZCoq